Repository navigation
AI models show their provider's brand logo in the model picker - #407
Conversation
Every LanguageModel node carried a generic "Sparkle" icon (and every ModelProvider a "Key"), so the picker rendered the same mark for every model. Add per-provider brand icons: 13 tile SVGs (brand colour + white glyph, matching the existing NodeType icon design) under MeshWeaver.Graph/Icons, plus a ModelProviderIcons resolver that maps a model to its maker by the model id first, then the provider name — so a Claude served through Azure or OpenRouter still reads as Anthropic. Wired into BuiltInLanguageModelProvider; unknown models fall back to the neutral sparkle. Adds a unit test and a What's New entry. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR improves the AI model picker UX by assigning provider/maker-specific brand icons to ModelProvider and LanguageModel catalog nodes, instead of showing the same generic icon for everything. It adds a small resolver in MeshWeaver.AI to infer the correct icon (model-id first, then provider name) and ships the corresponding SVG assets via the existing embedded NodeTypeIcons static content collection.
Changes:
- Add
ModelProviderIconsresolver to map model IDs / provider names to/static/NodeTypeIcons/{brand}.svg. - Wire the resolver into
BuiltInLanguageModelProviderso seeded Provider/Model nodes get brand logos. - Add brand SVGs and a dedicated unit test suite for icon resolution, plus a “What’s New” entry.
Reviewed changes
Copilot reviewed 4 out of 17 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| test/MeshWeaver.AI.Test/ModelProviderIconsTest.cs | Adds unit coverage for icon inference (model-id precedence, gateway cases, unknown fallback). |
| src/MeshWeaver.AI/ModelProviderIcons.cs | Introduces token-based resolver returning /static/NodeTypeIcons/*.svg paths for known brands. |
| src/MeshWeaver.AI/BuiltInLanguageModelProvider.cs | Uses the resolver when emitting ModelProvider and LanguageModel catalog nodes. |
| src/MeshWeaver.Graph/Icons/anthropic.svg | Adds Anthropic brand tile icon (embedded, served under /static/NodeTypeIcons/). |
| src/MeshWeaver.Graph/Icons/openai.svg | Adds OpenAI brand tile icon. |
| src/MeshWeaver.Graph/Icons/google.svg | Adds Google (Gemini/Gemma) brand tile icon. |
| src/MeshWeaver.Graph/Icons/mistral.svg | Adds Mistral brand tile icon. |
| src/MeshWeaver.Graph/Icons/meta.svg | Adds Meta (Llama) brand tile icon. |
| src/MeshWeaver.Graph/Icons/deepseek.svg | Adds DeepSeek brand tile icon. |
| src/MeshWeaver.Graph/Icons/xai.svg | Adds xAI (Grok) brand tile icon. |
| src/MeshWeaver.Graph/Icons/qwen.svg | Adds Qwen brand tile icon. |
| src/MeshWeaver.Graph/Icons/perplexity.svg | Adds Perplexity brand tile icon. |
| src/MeshWeaver.Graph/Icons/githubcopilot.svg | Adds GitHub Copilot brand tile icon. |
| src/MeshWeaver.Graph/Icons/openrouter.svg | Adds OpenRouter brand tile icon. |
| src/MeshWeaver.Graph/Icons/ollama.svg | Adds Ollama brand tile icon. |
| src/MeshWeaver.Graph/Icons/azure.svg | Adds Azure brand tile icon. |
| src/MeshWeaver.Documentation/Data/WhatsNew/2026-07-10-model-provider-icons.md | Documents the picker change in “What’s New”. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Brand logo when the provider name resolves to a known maker; the | ||
| // generic key otherwise. See ModelProviderIcons. | ||
| Icon = ModelProviderIcons.ForProvider(source.ProviderName) ?? "Key", |
| // Brand logo inferred from the model id (then provider) so a model reads | ||
| // as its maker; the generic sparkle when unknown. See ModelProviderIcons. | ||
| Icon = ModelProviderIcons.ForModel(source.ProviderName, modelId) ?? "Sparkle", |
Test Results (shard 4) 9 files ± 0 9 suites ±0 3m 8s ⏱️ +38s Results for commit 7044244. ± Comparison against base commit 1373ae5. This pull request removes 41 and adds 27 tests. Note that renamed tests count towards both.♻️ This comment has been updated with latest results. |
Test Results 58 files + 1 58 suites +1 22m 58s ⏱️ + 3m 49s Results for commit 7044244. ± Comparison against base commit 1373ae5. This pull request removes 41 and adds 342 tests. Note that renamed tests count towards both.♻️ This comment has been updated with latest results. |
Addresses Copilot review: MeshNodeImageHelper filters out legacy Fluent icon names (Key/Sparkle), so fall back to the /static/NodeTypeIcons/*.svg URL directly, keeping node.Icon always renderable. No rendered change (the legacy names already resolved to those same SVGs via the NodeType default). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Thanks @copilot. Addressed the first point in 7044244 — the fallbacks now use the self-contained On the second point (retrofit): correct — because |
…the hub's core Found while scanning src/ for hand-woven async. MessageHub carried a whole debug tracing rig, introduced with #407 (2026-07-10) for the teardown-SIGSEGV hunt and never removed: static readonly bool DisposeTraceEnabled // env var static readonly string DisposeTraceLogPath // a file in the temp dir static readonly Channel<string>? DisposeTraceChannel static MessageHub() { _ = Task.Run(async () => ...append to file forever...); } Three problems, in the most load-bearing class in the repo: * A static mutable Channel<string> is exactly the process-wide state AGENTS.md forbids — it survives every mesh disposal and is shared by every hub in the process. * `_ = Task.Run(...)` in a static constructor is an unobserved, never-cancelled background loop that lives for the lifetime of the process. * It is debug instrumentation shipped in production code, writing to a temp file. Removed entirely — fields, static ctor, the DisposeTrace method and all nine call sites. Nothing outside MessageHub.cs referenced it. No diagnostic is lost. Five of the nine call sites sat directly beside a logger/TryLog line carrying the same information; those are simply gone. The three in the hosted-dispose path had NO logging of their own, so they become real log lines rather than disappearing — and the error arm is now a Warning WITH the exception, where it previously recorded a hosted-hub disposal fault only into an off-by-default trace file, i.e. effectively nowhere. That needed a TryLog overload taking an Exception so the stack trace survives instead of being flattened into the message. Also dropped the now-dead workflow plumbing that collected the trace file (dotnet-test, alc-unload-probe, flake-repro). No log LEVELS were changed: the added lines are Debug for phase boundaries (not shipped to Loki) and Warning for the fault. MeshWeaver.Messaging.Hub.Test: 153/153. Solution builds clean under -c Release -p:CIRun=true -warnaserror. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hx6BiUYBu5U2Zdnom4Uz4X
Problem
Every AI model rendered an identical sparkle in the picker:
BuiltInLanguageModelProviderstampedIcon = "Sparkle"on everyLanguageModelnode andIcon = "Key"on everyModelProvider. Providers were indistinguishable at a glance.The picker (
MeshNodePickerControl) already renders each node'sIcon(URL / data-URI / inline-SVG / emoji), so the fix is purely to give the nodes real icons — no picker changes.What this does
src/MeshWeaver.Graph/Icons/(embedded → served at/static/NodeTypeIcons/), built to match the existing icon design exactly: a brand-colouredrx=4tile with a white glyph, so they're theme-independent by construction. Marks are from simple-icons (MIT), used nominatively.ModelProviderIconsresolver — maps a model to its maker by substring, model id first, then provider name. That ordering means aclaude-*served through AzureFoundry, oranthropic/claude-*via OpenRouter, still resolves to Anthropic. Unknown →null→ falls back to the neutral sparkle. Lives inMeshWeaver.AI(notGraph) to keep the generic layer free of provider knowledge.BuiltInLanguageModelProvider(the two icon assignments).Tests
ModelProviderIconsTest— 23 cases: per-brand model-id mapping, the gateway case (AzureFoundry → Claude → Anthropic), OpenRouter-prefixed ids, provider-name fallback, and unknown → null. Green under-c Release -warnaserror.Scope note
Covers the platform catalog (what fills the picker for everyone). BYO models a user creates via the generic
createpath aren't auto-stamped yet — the picker readsnode.Icon, so wiring the same resolver into the "add your own provider" flow is a clean follow-up.🤖 Generated with Claude Code